package tom.vertx.core.demo06;

import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpStatusClass;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.*;
import io.vertx.core.http.impl.HttpFrameImpl;
import lombok.extern.slf4j.Slf4j;

/**
 * @author ZHUFEIFEI
 */
@Slf4j
public class MyHttpClient extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    HttpClientOptions httpClientOptions = new HttpClientOptions()
      //使用http2, 不启用tls时,请求使用http1.1但是连接成功后会尝试发送http2的请求
      .setProtocolVersion(HttpVersion.HTTP_2)
      .setConnectTimeout(2000)
      .setTryUseCompression(true)
      //max connections for each host
//      .setMaxPoolSize(10)
      //针对http2配置，默认http2只使用一个连接连接到某个服务器，所有请求都通过一个链接。此处配置为3个连接，同一时间10个请求可以复用同一个连接
//      .setHttp2MaxPoolSize(3)
//      .setHttp2MultiplexingLimit(10)
      //针对http2的初始化配置
//      .setInitialSettings(new Http2Settings().setMaxConcurrentStreams(100))
      //websocket frame size
//      .setMaxWebsocketFrameSize(65535)
      .setKeepAlive(true).setReusePort(true);

    //client实例应每个verticle使用一个，不应共用，因为client绑定了本context
    HttpClient client = vertx.createHttpClient(httpClientOptions);
    client.redirectHandler(resp -> {
      log.info("redirect handler => {} {}", resp.request().absoluteURI(), resp.getHeader("Location"));
      // Only follow 301 code
      if (HttpStatusClass.REDIRECTION.contains(resp.statusCode()) && resp.getHeader("Location") != null) {
        // Create a new ready to use request that the client will use
        return Future.succeededFuture(client.getAbs("http://baidu.com"));
      }
      //don't redirect
      return null;
    });

    client.get(8080, "localhost", "/hello")
      .exceptionHandler(exp -> {
        log.warn("get exception occur -> {}", exp.toString());
      })
      .handler(resp -> {
        resp.endHandler(end -> {
          log.info("get response => {} {}", resp.statusCode(), resp.version());
        });
      }).end(res -> {
      if (res.succeeded()) {
        log.info("get result => {}", res.result());
      } else {
        log.warn("get failed => {}", res.cause().toString());
      }
    });

    client.get("baidu.com", "/")
      .exceptionHandler(exp -> {
        log.warn("get1 exception occur -> {}", exp.toString());
      })
      .handler(resp -> {
        resp.endHandler(end -> {
          log.info("get1 response => {} {}", resp.statusCode(), resp.version());
        });
      }).end(res -> {
      if (res.succeeded()) {
        log.info("get1 result => {}", res.result());
      } else {
        log.warn("get1 failed => {}", res.cause().toString());
      }
    });

    client.head("baidu.com", "/")
      .exceptionHandler(exp -> {
        log.warn("head exception occur -> {}", exp.toString());
      })
      .handler(resp -> {
        resp.endHandler(end -> {
          log.info("head response => {} {}", resp.statusCode(), resp.version());
        });
      }).end(res -> {
      if (res.succeeded()) {
        log.info("head result => {}", res.result());
      } else {
        log.warn("head failed => {}", res.cause().toString());
      }
    });

    client.request(HttpMethod.GET, "baidu.com", "/c")
      .exceptionHandler(exp -> {
        log.warn("request exception occur -> {}", exp.toString());
      })
      .setFollowRedirects(true)
      .handler(resp -> {
        resp.endHandler(end -> {
          log.info("request response => {} {} {} {}", resp.statusCode(), resp.version(), resp.request().absoluteURI(), resp.getHeader("Location"));
        });
      })
      .end(res -> {
      if (res.succeeded()) {
        log.info("request result => {}", res.result());
      } else {
        log.warn("request failed => {}", res.cause().toString());
      }
    });

    client.post("baidu.com", "/abc")
      .exceptionHandler(exp -> {
        log.warn("post exception occur -> {}", exp.toString());
      })
      .handler(resp -> {
        resp.endHandler(end -> {
          log.info("post response => {} {}", resp.statusCode(), resp.version());
        });
      })
      //使用write, 不指定content-length， 需要指定chunked头， 代表分块传输, 用户大数据量传输和未知大小传输
      .setChunked(true)
      .write("hello")
      .end(res -> {
        if (res.succeeded()) {
          log.info("post result => {}", res.result());
        } else {
          log.warn("post failed => {}", res.cause().toString());
        }
      });

    //先发送一个头，得到响应后再继续发送其他数据
    HttpClientRequest req = client.put("baidu.com", "/abc", resp -> {
      log.info("put response => {}", resp.statusCode());
    })
      .exceptionHandler(exp -> {
        log.warn("put exception occur -> {}", exp.toString());
      })
      .putHeader(HttpHeaders.EXPECT, HttpHeaderValues.CONTINUE);
    req.continueHandler(res -> {
        req.write("hello");
        req.write("world");
      //end结束本次请求
        req.end();
      });

    //处理server push过来的信息，仅支持http2
    client.get(8888, "localhost", "/hello", resp -> {
      log.info("get3 response => {} {}", resp.statusCode(), resp.version());
    })
      .pushHandler(push -> {
        log.info("server pushed => {}", push.path());
        push.handler(resp -> {
          log.info("response for pushed request => {}", resp.statusCode());
          resp.bodyHandler(body -> {
            log.info("server pushed data => {}", body.toString());
          });
        });
      })
      .end();

    HttpClientRequest req2 = client.get(8888, "localhost", "/abc", resp -> {
      log.info("get4 response => {} {}", resp.statusCode(), resp.version());
      //仅支持http2
      resp.customFrameHandler(frame -> {
        log.info("receive frame => {} payload => {}", frame.type(), frame.payload().toString());
      });
    });
    req2.sendHead(res -> {
      log.info("sendHead => {}", res);
      req2.writeCustomFrame(1,2,Buffer.buffer("What"));
      req2.end(r -> {
        if (r.succeeded()) {
          log.info("post result => {}", r.result());
        } else {
          log.warn("post failed => {}", r.cause().toString());
        }
      });
    });

    client.webSocket(8888,"localhost","/abc", res -> {
      if (res.succeeded()) {
        log.info("websocket connected.");
      } else {
        log.warn("websocket connect failed => {}", res.cause().toString());
      }
    });

    client.webSocket(new WebSocketConnectOptions().setHost("localhost").setPort(8888).addHeader("username", "abc").setURI("/wskt"), res -> {
      if (res.succeeded()) {
        log.info("websocket1 connected.");
        res.result().exceptionHandler(exp -> {
          log.warn("websocket1 exception -> {}", exp.toString());
        });
        res.result().closeHandler(ha -> {
          log.info("websocket1 closed.");
        });
        //length < maxWebSocketFrameSize, final为true, 默认发送的数据小于65535时，不需要对数据进行切分发送多帧，大于该值时，切分成多帧，除最后一帧其他的帧final=false
        res.result().writeTextMessage("Hello Websocket!");
        res.result().writeTextMessage("Hello Websocket2!");
        //写多帧数据，中间帧continue Frame, 最后一帧要标记为final
        WebSocketFrame frame1 = WebSocketFrame.binaryFrame(Buffer.buffer("Hello Websocket Frame!"), false);
        WebSocketFrame frame2 = WebSocketFrame.continuationFrame(Buffer.buffer("Hello Websocket Frame2!"), true);
        res.result().writeFrame(frame1);
        res.result().writeFrame(frame2);
        res.result().writeFinalTextFrame("Geronimo!");
      } else {
        log.warn("websocket1 connect failed => {}", res.cause().toString());
      }
    });

    startPromise.complete();
  }

  public static void main(String[] args) {
    Vertx.vertx().deployVerticle(new MyHttpClient(), res -> {
      if (res.succeeded()) {
        log.info("deploy succeed.");
      } else {
        log.error("deploy error.", res.cause());
      }
    });
  }
}
